/*
 * Copyright 2020 NAVER Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.navercorp.pinpoint.it.plugin.jdbc.mssql;

import com.navercorp.pinpoint.bootstrap.context.DatabaseInfo;
import com.navercorp.pinpoint.bootstrap.plugin.jdbc.DatabaseInfoAccessor;
import com.navercorp.pinpoint.bootstrap.plugin.test.PluginTestVerifier;
import com.navercorp.pinpoint.bootstrap.plugin.test.PluginTestVerifierHolder;
import com.navercorp.pinpoint.it.plugin.utils.AgentPath;
import com.navercorp.pinpoint.it.plugin.utils.PluginITConstants;
import com.navercorp.pinpoint.it.plugin.utils.TestcontainersOption;
import com.navercorp.pinpoint.it.plugin.utils.jdbc.DriverManagerUtils;
import com.navercorp.pinpoint.it.plugin.utils.jdbc.JDBCDriverClass;
import com.navercorp.pinpoint.it.plugin.utils.jdbc.JDBCTestConstants;
import com.navercorp.pinpoint.test.plugin.Dependency;
import com.navercorp.pinpoint.test.plugin.PinpointAgent;
import com.navercorp.pinpoint.test.plugin.PinpointConfig;
import com.navercorp.pinpoint.test.plugin.PluginTest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.AutoClose;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.MSSQLServerContainer;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import static com.navercorp.pinpoint.it.plugin.utils.jdbc.JdbcUtils.doInTransaction;
import static com.navercorp.pinpoint.it.plugin.utils.jdbc.JdbcUtils.fetchResultSet;

/**
 * @author Woonduk Kang(emeroad)
 */
@PluginTest
@PinpointAgent(AgentPath.PATH)
@Dependency({"com.microsoft.sqlserver:mssql-jdbc:[7.0.0.jre8]", PluginITConstants.VERSION, JDBCTestConstants.VERSION, TestcontainersOption.TEST_CONTAINER, TestcontainersOption.MSSQL})
@PinpointConfig("pinpoint-mssql.config")
public class MSSSqlConnectionIT {

    private static final Logger logger = LogManager.getLogger(MSSSqlConnectionIT.class);

    private static JDBCDriverClass driverClass;
    @AutoClose("stop")
    public static final MSSQLServerContainer<?> mssqlserver = MSSQLServerContainerFactory.newMSSQLServerContainer(logger.getName());

    @BeforeAll
    public static void beforeClass() throws Exception {
        Assumptions.assumeTrue(DockerClientFactory.instance().isDockerAvailable(), "Docker not enabled");

        mssqlserver.start();

        driverClass = new MSSqlJDBCDriverClass();
    }

    @BeforeEach
    public void registerDriver() throws Exception {
        Driver driver = driverClass.newDriver();
        DriverManager.registerDriver(driver);
    }

    @AfterEach
    public void deregisterDriver() {
        DriverManagerUtils.deregisterDriver();
    }

    @Test
    public void executeQueryAndExecuteUpdate() throws SQLException {
        try (Connection connection = connectDB();
             PreparedStatement preparedStatement = connection.prepareStatement("select 1")) {
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                if (resultSet.next()) {
                    logger.debug("---{}", resultSet.getObject(1));
                }
            }
        }
    }

    private Connection connectDB() throws SQLException {
        Properties properties = new Properties();
        properties.setProperty("user", mssqlserver.getUsername());
        properties.setProperty("password", mssqlserver.getPassword());
        return DriverManager.getConnection(mssqlserver.getJdbcUrl(), properties);
    }


    @Test
    public void testModify() throws Exception {
        logger.debug("testModify");

        Connection capture;
        try (Connection connection = connectDB()) {
            capture = connection;
            logger.debug("Connection class name:{}", connection.getClass().getName());
            logger.debug("Connection class cl:{}", connection.getClass().getClassLoader());

            DatabaseInfo url = ((DatabaseInfoAccessor) connection)._$PINPOINT$_getDatabaseInfo();
            Assertions.assertNotNull(url);

            statement(connection);

            preparedStatement(connection);

            preparedStatement2(connection);

            preparedStatement3(connection);

            preparedStatement4(connection);

            preparedStatement5(connection);

            preparedStatement6(connection);

            preparedStatement7(connection);

            preparedStatement8(connection);
        }

        DatabaseInfo clearUrl = ((DatabaseInfoAccessor) capture)._$PINPOINT$_getDatabaseInfo();
        Assertions.assertNull(clearUrl);

        PluginTestVerifier verifier = PluginTestVerifierHolder.getInstance();
        verifier.printCache();
    }


    private void statement(Connection connection) throws SQLException {
        try (Statement statement = connection.createStatement()) {
            try (ResultSet resultSet = statement.executeQuery("select 1")) {
                fetchResultSet(resultSet);
            }
        }
    }

    private void preparedStatement(Connection connection) throws SQLException {
        try (PreparedStatement preparedStatement = connection.prepareStatement("select 1")) {
            logger.debug("PreparedStatement className:{}", preparedStatement.getClass().getName());
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                fetchResultSet(resultSet);
            }
        }
    }


    private void preparedStatement2(Connection connection) throws SQLException {
        try (PreparedStatement preparedStatement = connection.prepareStatement("select * from member where id = ?")) {
            preparedStatement.setInt(1, 1);
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                fetchResultSet(resultSet);
            }
        }
    }

    private void preparedStatement3(Connection connection) throws SQLException {
        doInTransaction(connection, () -> {
            try (PreparedStatement preparedStatement = connection.prepareStatement("select * from member where id = ? or id = ?  or id = ?")) {
                preparedStatement.setInt(1, 1);
                preparedStatement.setInt(2, 2);
                preparedStatement.setString(3, "3");
                try (ResultSet resultSet = preparedStatement.executeQuery()) {
                    fetchResultSet(resultSet);
                }
            }
        });
    }

    private void preparedStatement4(Connection connection) throws SQLException {
//        Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS
        try (PreparedStatement preparedStatement = connection.prepareStatement("select 1", Statement.RETURN_GENERATED_KEYS)) {
            logger.debug("PreparedStatement className:{}", preparedStatement.getClass().getName());
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                fetchResultSet(resultSet);
            }
        }
    }

    private void preparedStatement5(Connection connection) throws SQLException {
//        Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS
        try (PreparedStatement preparedStatement = connection.prepareStatement("select 1", new String[]{"test"})) {
            logger.info("PreparedStatement className:{}", preparedStatement.getClass().getName());
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                fetchResultSet(resultSet);
            }
        }
    }

    private void preparedStatement6(Connection connection) throws SQLException {
//        Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS
        int[] columnIndex = {1};
        try (PreparedStatement preparedStatement = connection.prepareStatement("select 1", columnIndex)) {
            logger.info("PreparedStatement className:{}", preparedStatement.getClass().getName());
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                fetchResultSet(resultSet);
            }
        }
    }

    private void preparedStatement7(Connection connection) throws SQLException {
//        Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS
        try (PreparedStatement preparedStatement = connection.prepareStatement("select 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
            logger.info("PreparedStatement className:{}", preparedStatement.getClass().getName());
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                fetchResultSet(resultSet);
            }
        }
    }

    private void preparedStatement8(Connection connection) throws SQLException {
//        Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS
//        ResultSet.HOLD_CURSORS_OVER_COMMIT or ResultSet.CLOSE_CURSORS_AT_COMMIT
        try (PreparedStatement preparedStatement = connection.prepareStatement("select 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT)) {
            logger.info("PreparedStatement className:{}", preparedStatement.getClass().getName());
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                fetchResultSet(resultSet);
            }
        }
    }

}